import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.util.concurrent.ThreadFactory;

import junit.framework.TestCase;

import org.ourgrid.threadServices.MonitoredThreadFactory;
import org.ourgrid.threadServices.ThreadServices;
import org.ourgrid.threadServices.ThreadServicesFactory;

public class Kurose24Cap4RouterTest extends TestCase {

	private static final byte U = 0;

	private static final byte V = 1;

	private static final byte X = 2;

	private static final byte Y = 3;

	private static final byte Z = 4;

	private final int basePort = 2000;

	private ThreadFactory threadFactory;

	private Router routerU;

	private Router routerV;

	private Router routerX;

	private Router routerY;

	private Router routerZ;

	private Thread threadRouterU;

	private Thread threadRouterV;

	private Thread threadRouterX;

	private Thread threadRouterY;

	private Thread threadRouterZ;

	private InetSocketAddress socketAddressU;

	private InetSocketAddress socketAddressV;

	private InetSocketAddress socketAddressX;

	private InetSocketAddress socketAddressY;

	private InetSocketAddress socketAddressZ;

	private InetSocketAddress[ ] neighboursU;

	private InetSocketAddress[ ] neighboursV;

	private InetSocketAddress[ ] neighboursX;

	private InetSocketAddress[ ] neighboursY;

	private InetSocketAddress[ ] neighboursZ;

	private int[ ] distanceVectorU;

	private int[ ] distanceVectorV;

	private int[ ] distanceVectorX;

	private int[ ] distanceVectorY;

	private int[ ] distanceVectorZ;


	public static void main( String[ ] args ) {

		junit.swingui.TestRunner.run( Kurose24Cap4RouterTest.class );
	}


	public Kurose24Cap4RouterTest( String name ) {

		super( name );
	}


	protected void setUp() throws Exception {

		this.threadFactory = new MonitoredThreadFactory( this.getName() );

		this.routerU = new Router( (byte) U, basePort + 0, "127.0.0.1" );
		this.routerV = new Router( (byte) V, basePort + 1, "127.0.0.1" );
		this.routerX = new Router( (byte) X, basePort + 2, "127.0.0.1" );
		this.routerY = new Router( (byte) Y, basePort + 3, "127.0.0.1" );
		this.routerZ = new Router( (byte) Z, basePort + 4, "127.0.0.1" );
		this.threadRouterU = this.threadFactory.newThread( this.routerU );
		this.threadRouterV = this.threadFactory.newThread( this.routerV );
		this.threadRouterX = this.threadFactory.newThread( this.routerX );
		this.threadRouterY = this.threadFactory.newThread( this.routerY );
		this.threadRouterZ = this.threadFactory.newThread( this.routerZ );

		this.socketAddressU = new InetSocketAddress( "127.0.0.1", basePort + 0 );
		this.socketAddressV = new InetSocketAddress( "127.0.0.1", basePort + 1 );
		this.socketAddressX = new InetSocketAddress( "127.0.0.1", basePort + 2 );
		this.socketAddressY = new InetSocketAddress( "127.0.0.1", basePort + 3 );
		this.socketAddressZ = new InetSocketAddress( "127.0.0.1", basePort + 4 );

		this.neighboursU = new InetSocketAddress[ ] { Router.LOCALHOST, this.socketAddressV, this.socketAddressX,
														Router.NOT_NEIGHBOR, Router.NOT_NEIGHBOR };
		this.distanceVectorU = new int[ ] { Router.LOCALHOST_COST, 1, 2, Router.NOT_NEIGHBOR_COST,
											Router.NOT_NEIGHBOR_COST };

		this.neighboursV = new InetSocketAddress[ ] { this.socketAddressU, Router.LOCALHOST, Router.NOT_NEIGHBOR,
														this.socketAddressY, this.socketAddressZ };
		this.distanceVectorV = new int[ ] { 1, Router.LOCALHOST_COST, Router.NOT_NEIGHBOR_COST, 15, 5 };

		this.neighboursX = new InetSocketAddress[ ] { this.socketAddressU, Router.NOT_NEIGHBOR, Router.LOCALHOST,
														this.socketAddressY, this.socketAddressZ };
		this.distanceVectorX = new int[ ] { 2, Router.NOT_NEIGHBOR_COST, Router.LOCALHOST_COST, 1, 2 };

		this.neighboursY = new InetSocketAddress[ ] { Router.NOT_NEIGHBOR, this.socketAddressV, this.socketAddressX,
														Router.LOCALHOST, this.socketAddressZ };
		this.distanceVectorY = new int[ ] { Router.NOT_NEIGHBOR_COST, 15, 1, Router.LOCALHOST_COST, 10 };

		this.neighboursZ = new InetSocketAddress[ ] { Router.NOT_NEIGHBOR, this.socketAddressV, this.socketAddressX,
														this.socketAddressY, Router.LOCALHOST };
		this.distanceVectorZ = new int[ ] { Router.NOT_NEIGHBOR_COST, 5, 2, 10, Router.LOCALHOST_COST };

		this.routerU.setInitialEnlaceState( this.distanceVectorU, this.neighboursU );
		this.routerV.setInitialEnlaceState( this.distanceVectorV, this.neighboursV );
		this.routerX.setInitialEnlaceState( this.distanceVectorX, this.neighboursX );
		this.routerY.setInitialEnlaceState( this.distanceVectorY, this.neighboursY );
		this.routerZ.setInitialEnlaceState( this.distanceVectorZ, this.neighboursZ );

		threadRouterU = this.threadFactory.newThread( routerU );
		threadRouterV = this.threadFactory.newThread( routerV );
		threadRouterX = this.threadFactory.newThread( routerX );
		threadRouterY = this.threadFactory.newThread( routerY );
		threadRouterZ = this.threadFactory.newThread( routerZ );

		super.setUp();
	}


	protected void tearDown() throws Exception {

		super.tearDown();
		this.routerU.shutdown();
		this.routerV.shutdown();
		this.routerX.shutdown();
		this.routerY.shutdown();
		this.routerZ.shutdown();
		Thread.sleep( 100 ); // wait OS sockets to be closed.
	}


	public void testUnderControlledEnviroment() throws InterruptedException {

		assertInitialDistances();
		startRouters();
		waitStabilization();
		assertFinalDistances();

	}


	private void waitStabilization() throws InterruptedException {

		Thread.sleep( 1000 );
	}


	private void startRouters() {

		this.threadRouterU.start();
		this.threadRouterV.start();
		this.threadRouterX.start();
		this.threadRouterY.start();
		this.threadRouterZ.start();
	}


	private void assertFinalDistances() {

		// u 0 1 2 3 4
		// v 1 0 3 4 5
		// x 2 3 0 1 2
		// y 3 4 1 0 3
		// z 4 5 2 3 0
		assertEquals( 0, this.routerU.getDistanceVector()[U] );
		assertEquals( 1, this.routerU.getDistanceVector()[V] );
		assertEquals( 2, this.routerU.getDistanceVector()[X] );
		assertEquals( 3, this.routerU.getDistanceVector()[Y] );
		assertEquals( 4, this.routerU.getDistanceVector()[Z] );

		assertEquals( 1, this.routerV.getDistanceVector()[U] );
		assertEquals( 0, this.routerV.getDistanceVector()[V] );
		assertEquals( 3, this.routerV.getDistanceVector()[X] );
		assertEquals( 4, this.routerV.getDistanceVector()[Y] );
		assertEquals( 5, this.routerV.getDistanceVector()[Z] );

		assertEquals( 2, this.routerX.getDistanceVector()[U] );
		assertEquals( 3, this.routerX.getDistanceVector()[V] );
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( 1, this.routerX.getDistanceVector()[Y] );
		assertEquals( 2, this.routerX.getDistanceVector()[Z] );

		assertEquals( 3, this.routerY.getDistanceVector()[U] );
		assertEquals( 4, this.routerY.getDistanceVector()[V] );
		assertEquals( 1, this.routerY.getDistanceVector()[X] );
		assertEquals( 0, this.routerY.getDistanceVector()[Y] );
		assertEquals( 3, this.routerY.getDistanceVector()[Z] );

		assertEquals( 4, this.routerZ.getDistanceVector()[U] );
		assertEquals( 5, this.routerZ.getDistanceVector()[V] );
		assertEquals( 2, this.routerZ.getDistanceVector()[X] );
		assertEquals( 3, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
	}


	private void assertInitialDistances() {

		assertEquals( 0, this.routerU.getDistanceVector()[U] );
		assertEquals( 1, this.routerU.getDistanceVector()[V] );
		assertEquals( 2, this.routerU.getDistanceVector()[X] );
		assertEquals( Router.INFINITY, this.routerU.getDistanceVector()[Y] );
		assertEquals( Router.INFINITY, this.routerU.getDistanceVector()[Z] );

		assertEquals( 1, this.routerV.getDistanceVector()[U] );
		assertEquals( 0, this.routerV.getDistanceVector()[V] );
		assertEquals( Router.INFINITY, this.routerV.getDistanceVector()[X] );
		assertEquals( 15, this.routerV.getDistanceVector()[Y] );
		assertEquals( 5, this.routerV.getDistanceVector()[Z] );

		assertEquals( 2, this.routerX.getDistanceVector()[U] );
		assertEquals( Router.INFINITY, this.routerX.getDistanceVector()[V] );
		assertEquals( 0, this.routerX.getDistanceVector()[X] );
		assertEquals( 1, this.routerX.getDistanceVector()[Y] );
		assertEquals( 2, this.routerX.getDistanceVector()[Z] );

		assertEquals( Router.INFINITY, this.routerY.getDistanceVector()[U] );
		assertEquals( 15, this.routerY.getDistanceVector()[V] );
		assertEquals( 1, this.routerY.getDistanceVector()[X] );
		assertEquals( 0, this.routerY.getDistanceVector()[Y] );
		assertEquals( 10, this.routerY.getDistanceVector()[Z] );

		assertEquals( Router.INFINITY, this.routerZ.getDistanceVector()[U] );
		assertEquals( 5, this.routerZ.getDistanceVector()[V] );
		assertEquals( 2, this.routerZ.getDistanceVector()[X] );
		assertEquals( 10, this.routerZ.getDistanceVector()[Y] );
		assertEquals( 0, this.routerZ.getDistanceVector()[Z] );
	}

}
